Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 410 Report Verification - LFO #2714

Merged
merged 7 commits into from
Feb 4, 2025
Merged

Conversation

shon-button
Copy link
Contributor

@shon-button shon-button commented Jan 21, 2025

Addresses 410

🚀 Impact:

  • Model Splitting: The ReportVerification model was split to move visit-related fields (visit_name, visit_type, other_facility_name, other_facility_coordinates) into a new model, ReportVerificationVisit
  • New Relationship: ForeignKey relationship from ReportVerificationVisit to ReportVerification
  • New Constraint: CheckConstraint in ReportVerificationVisit to enforce that visit_coordinates must be provided when is_other_visit=True
  • Database schemas: updated to introduce the new verification_visit table

📝 To Dos:

  • Improve tests, see 541
  • Possibly refactor the RJSF schema(s) to apply all rules, unless this is not possible when applying rules based on dynamic values. See tech debt ticket for more details: 536

🔬 Local Testing:

  1. From terminal command, start the api server:
cd bc_obps
make reset_db
make run
  1. From terminal command, start the app development server:
cd bciers && yarn dev-all

Tests SFO "Site(s) Visited" conditional fields*

  1. Navigate to http://localhost:3000
  2. Click button "Log in with Business BCeID"
  3. Navigate to Bugle SFO - Registered verification step: http://localhost:3000/reporting/reports/1/verification
  4. Complete the form with field value Site(s) Visited= "None"
  5. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to Bugle SFO - Registered verification step: http://localhost:3000/reporting/reports/1/verification
    Expected Results
  • Form data is displayed from the database
    image
  1. Complete the form with field value Site(s) Visited= "Other"
  2. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to Bugle SFO - Registered verification step: http://localhost:3000/reporting/reports/1/verification
    Expected Results
  • Form data is displayed from the database
    image
  1. Complete the form with field value Site(s) Visited= "Facility 22"
  2. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to Bugle SFO - Registered verification step: http://localhost:3000/reporting/reports/1/verification
    Expected Results
  • Form data is displayed from the database
    image

Tests LFO "Site(s) Visited" conditional fields*

  1. Navigate to http://localhost:3000
  2. Click button "Log in with Business BCeID"
  3. Navigate to Banana LFO - Registered verification step: http://localhost:3000/reporting/reports/2/verification
  4. Complete the form with field value Site(s) Visited= "None"
  5. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to Banana LFO - Registered verification step: http://localhost:3000/reporting/reports/2/verification
    Expected Results
  • Form data is displayed from the database
    image
  1. Complete the form with field value Site(s) Visited= "Other"
  2. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to ``Banana LFO - Registered verification step: http://localhost:3000/reporting/reports/2/verification
    Expected Results
  • Form data is displayed from the database
    image
  1. Complete the form with field value Site(s) Visited= "Facility 1"
  2. Click button Save & Continue
    Expected Results
  • Form data is saved to the database
  • Attachment page is displayed
  1. Navigate to Banana LFO - Registered verification step: http://localhost:3000/reporting/reports/2/verification
    Expected Results
  • Form data is displayed from the database
    image

@shon-button shon-button force-pushed the feat/410-verification-lfo branch 8 times, most recently from 1c5ce43 to 26f888f Compare January 24, 2025 20:45
Copy link
Contributor

@pbastia pbastia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!! I've left a few comments, mostly small stuff.

I think there's some RJSF features that we can use to cut down on the schema/data manipulation, ideally we're writing code where only the form drives the data, and the components only needs to (hopefully not too much) transform it before submitting it to the API

constraints = [
models.CheckConstraint(
name="other_facility_must_have_coordinates",
check=~Q(is_other_visit=True) | Q(visit_coordinates__isnull=False),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to exclude the case "is_other_visit=True and visit_coordinates__isnull=True"

check=~(Q(is_other_visit=True) & Q(visit_coordinates__isnull=True))

Comment on lines 11 to 15
verification_body_name: str = Field(...)
accredited_by: str = Field(...)
scope_of_verification: str = Field(...)
threats_to_independence: bool = Field(...)
verification_conclusion: str = Field(...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are redundant with the fields = ... in the Meta class, we can just delete these lines

Comment on lines 32 to 35
visit_name: str = Field(...)
visit_type: str = Field(choices=ReportVerificationVisit.VisitType.choices)
is_other_visit: bool = Field(...)
visit_coordinates: str = Field(required=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, I think the choices and required options will be inferred from the model if we just remove these lines

report_verification_visits: List[ReportVerificationVisitSchema] = Field(default_factory=list)

class Meta(BaseReportVerification.Meta):
fields = BaseReportVerification.Meta.fields + ['report_version']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all these schemas we could actually generate them dynamically, since they're just a subset of the fields on the model: https://django-ninja.dev/guides/response/django-pydantic-create-schema/

@@ -13,17 +15,19 @@ class ReportVerificationService:
@staticmethod
def get_report_verification_by_version_id(
report_version_id: int,
) -> ReportVerification:
) -> ReportVerificationOut:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider decoupling the service layer from the API layer who owns these data structures. The service layer can declare similar types, or just return the Model types

Comment on lines 85 to 83
# Fetch ReportVerificationVisit instances corresponding to the IDs
visit_instances_to_keep = ReportVerificationVisit.objects.filter(id__in=visit_ids_to_keep)

# Update report_verification_visits
report_verification.report_verification_visits.set(visit_instances_to_keep)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary, all the ReportVerificationVisit models have been created with the right report_verification field set, and this is not an m2m relationship.
We can remove these few lines

Comment on lines 14 to 25
# Create related ReportVerificationVisit instances and link them
report_verification_visits = baker.make_recipe(
"reporting.tests.utils.report_verification_visit",
_quantity=2,
)

# Attach the visits to the report_verification instance
self.report_verification.report_verification_visits.set(report_verification_visits)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Create related ReportVerificationVisit instances and link them
report_verification_visits = baker.make_recipe(
"reporting.tests.utils.report_verification_visit",
_quantity=2,
)
# Attach the visits to the report_verification instance
self.report_verification.report_verification_visits.set(report_verification_visits)
# Create related ReportVerificationVisit instances and link them
report_verification_visits = baker.make_recipe(
"reporting.tests.utils.report_verification_visit",
report_verification=report_verification,
_quantity=2,
)

is_other_visit: true,
visit_coordinates: formData.visit_others.visit_coordinates || "",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure there is a way to bake this in to the rjsf structure instead, with if then else or oneOf / anyOf. Happy to try to pair on that if you want!

initialData.visit_types = undefined;
initialData.visit_others = [{}];
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like work that could be done on the server by the API layer (maybe in django ninja schema?): preparing the data for the form and the client page

@shon-button shon-button force-pushed the feat/410-verification-lfo branch 3 times, most recently from 3040f10 to 8485ae0 Compare January 27, 2025 19:26
@shon-button shon-button marked this pull request as ready for review January 27, 2025 19:27
@shon-button shon-button force-pushed the feat/410-verification-lfo branch 12 times, most recently from 487640d to e405d43 Compare February 3, 2025 17:34
Copy link
Contributor

@pbastia pbastia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I'm just leaving a couple of nitpicks, but this is ready to go otherwise :)

Comment on lines +35 to +38
visit_name: str
visit_type: Optional[str] = Field(None)
is_other_visit: bool
visit_coordinates: str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are inferred from the django model, they're redundant with the Meta fields = [...] declaration. We can just remove those lines

Copy link
Contributor Author

@shon-button shon-button Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pbastia
FYI: when I remove the "redundant fields" I get MyPy failed

Mypy.....................................................................Failed
reporting/service/report_verification_service.py:67:31: error: "ReportVerificationVisitSchema" has no attribute "visit_type"  [attr-defined]
reporting/service/report_verification_service.py:68:35: error: "ReportVerificationVisitSchema" has no attribute "is_other_visit"  [attr-defined]
reporting/service/report_verification_service.py:69:38: error: "ReportVerificationVisitSchema" has no attribute "visit_coordinates"  [attr-defined]
reporting/service/report_verification_service.py:74:28: error: "ReportVerificationVisitSchema" has no attribute "visit_name"  [attr-defined]

assert len(response_json["report_verification_visits"]) == len(payload.report_verification_visits)
for i, visit_data in enumerate(response_json["report_verification_visits"]):
expected_visit = payload.report_verification_visits[i]
print(expected_visit)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print left behind

Comment on lines 5 to 14
export const createVerificationUiSchema = (
schemaType: "SFO" | "LFO",
): RJSFSchema => {
// Determine the schema based on the schemaType
const localUiSchema: RJSFSchema =
schemaType === "SFO" ? { ...sfoUiSchema } : { ...lfoUiSchema };

// Return the customized schema.
return localUiSchema;
};
Copy link
Contributor

@pbastia pbastia Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the UiSchemas are only used in this one form, I think we can safely return the object directly

(also, the {...sfoUiSchema} syntax only allows for a shallow copy, so its usefulness is already debatable. This is why we write it as JSON.parse(JSON.stringify(stuff)) sometimes, although this will not work if we have functions in the object values)

Suggested change
export const createVerificationUiSchema = (
schemaType: "SFO" | "LFO",
): RJSFSchema => {
// Determine the schema based on the schemaType
const localUiSchema: RJSFSchema =
schemaType === "SFO" ? { ...sfoUiSchema } : { ...lfoUiSchema };
// Return the customized schema.
return localUiSchema;
};
export const getVerificationUiSchema = (
schemaType: "SFO" | "LFO",
): RJSFSchema => {
// Determine the schema based on the schemaType
const localUiSchema: RJSFSchema =
schemaType === "SFO" ? sfoUiSchema : lfoUiSchema;
// Return the customized schema.
return localUiSchema;
};

@shon-button shon-button force-pushed the feat/410-verification-lfo branch 4 times, most recently from fb25262 to d6c7c61 Compare February 3, 2025 21:02
chore: update verification service

chore: cleanup

chore: cleanup

test: verification api get

chore: add verification operation type

chore: ad shared verification schema properties

chore: add verification dependacncies

chore: shared schemas

chore: refactor SFO updateReportVerificationVisits

chore: cleanup

chore: cleanup
chore: cleanup

chore: cleanup

chore: cleanup
chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup
@shon-button shon-button force-pushed the feat/410-verification-lfo branch 2 times, most recently from b18eb7c to 0e30a94 Compare February 3, 2025 23:57
chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup

chore: cleanup

chore:cleanup

chore: cleanup

chore: revert
@shon-button shon-button force-pushed the feat/410-verification-lfo branch from 5fa3ca4 to c39bf13 Compare February 4, 2025 00:22
@shon-button shon-button merged commit 7db253a into develop Feb 4, 2025
41 of 42 checks passed
@shon-button shon-button deleted the feat/410-verification-lfo branch February 4, 2025 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants